如何用S7-200 SMART 编写一个属于自己的指令?可把我厉害坏了!
经常有人问,这个PLC有没有某某指令,或者,有没有指令可以实现这样那样的功能?
产生这样的疑问,部分原因是不熟悉这款PLC的指令。部分原因是为了选型,想找到一种PLC能够内置一些功能,使得自己的编程得到简化。
甚至有一些人认为,PLC没有这个指令就是没有这个功能,不能完成某个任务。如果指令能自己做,那就没有这个问题了。那我们就试着自己做自己需要的指令。
本系列文章围绕S7-200 SMART PLC为中心,分为三篇讲解如何编写自己需要的指令,编程语言主要以梯形图的方式为主。
注意:前方高能!超长文章,请大家拿着奶茶慢慢看~
一、扫描
梯形图的所有指令一般遵循扫描原则进行执行。执行一个完整的程序,过程中由于扫描有先后次序,所以必然在一个瞬间只能执行一个指令。那么当扫描到程序中间的时候,程序前部分的程序先获得扫描并执行过,执行的结果将已经产生影响。后面部分的程序未曾执行,将来的执行当中所有条件将以最新的形态执行。
首先来看一个例子。如图1所示:
图1程序
从I0.0 = False(0)之后的某个扫描周期开始看看扫描的过程。此时,所有变量都没有接通。
当有一个扫描周期I0.0 = True(1)的时候,并且方框处的程序刚刚处理完的时刻,第一段的Q0.0显然跟以前的扫描一样,不接通。而此时此刻的M0.0已经接通,它将开始影响以后程序的执行。然后在这个扫描周期结束的时候Q0.1将接通了,但是Q0.0却没有接通。
下一个周期开始,扫描第一个M0.0触点的时候,此时的M0.0才开始影响第一行程序。
所以,扫描到并执行出的结果是影响往后的所有扫描的。并没有扫描周期之分。所有执行都基于扫描。
二、能流
一行程序最左边粗实线是能流的源,这里永远是True(1)。通过各种触点的True(1)与False(0)组合,能流能到达右侧的线圈指令的话,就可以使线圈置位(1)。能流不能到达右侧线圈的话线圈就复位(0)。能扫描到的指令,基本能按照这种意愿执行。
要正确理解能流True(1)与False(0)两种状态对程序的影响,如图2所示。
图2程序
当I0.0 = True(1)的时候,两个指令都得到了能流。计时器指令按照自己的属性进行计时。传送指令把源地址数据传送到目的地址,无论目的地址原来有什么值都执行传送更新目的地址。
当I0.0 = False(0)的时候,两个指令都没有了能流。计时器在没有能流的时候把当前值写成0,把计时器位写成False(0)。值得注意的是,这个指令在False(0)的时候不是什么都不做的,而是在做False(0)时应该做的事情。有这样特点的指令还有输出线圈指令等。传送指令在没有能流的时候不再传送,不再理会目的地址。大量位于能流最右侧的指令都有这个特点。
三、双线圈
不追究这个词的历史背景了。反正大家都知道在一个程序里面对同一个线圈出现过两次输出线圈就是双线圈。
双线圈不会当作是一种错误程序。它完全可以按照程序的行为作出解释。也就是说它没有语法错误,但是可能会导致逻辑执行错误。
如图3所示的例子,可能作者希望自动动作时的三个步骤Q0.0得电。而且切换到点动的时候又可以控制Q0.0。结果,由于出现了双线圈,影响了期望的动作。这是双线圈最显著的错误使用。
图3程序
对于输出点Q,总是按照一个周期中最后一个输出指令刷新输出到物理点的。
下面举一个案例,看看怎么处理比较好?
有一个输出点,根据工艺已经做好了程序并且调试成功。程序片段如图4所示。
图4程序
突然甲方来了一个要求,要求Q0.0在原来的基础上并联一个现场按钮,无论Q0.0是否动作,现场按钮按下时,Q0.0都要为True(1)。现场按钮松开时,不影响原来的程序作用。同时要求,Q0.1不受现场按钮影响。于是如图5所示做了如下修改。
图5程序
Ok!程序没有问题。要是改成如图6所示的程序,结果会怎样?同样,这个程序也可以完成上述任务。
图6程序
这是一个双线圈的程序。第二个输出线圈由于带有第一个线圈的执行结果,触点先继承了前面所有的逻辑,在此逻辑下增加了一个现场按钮。解决了Q0.0的需求,但是又没有对Q0.1有任何的影响。程序却非常简单。
再看一个双线圈使用的例子。如图7所示:
图7程序
先看网络1 2 3。“报警”和“运行中”两个信号由于有公共相似的逻辑,所以用一个线圈把公共部分记录下来,再在下面的程序多次使用这样的公共部分。为了使程序简单一点,不用在报警和运行的线圈前都把相同的公共部分重复写一次。然而,网络4才是Q输出点最终要达成的控制逻辑目的。Q只是在前面的工作中临时客串,义务送了一个人情。临时变量的影子也悄悄地在这个例子中流露出来。
四、多重赋值
多重赋值是说明一个地址,在程序多个地方都有出现对其写入的操作。双线圈是一种典型的多重赋值。执行写入的主动操作者可以是用户程序,可以是系统行为,可以是来自通讯端口改写。还可以是其他。
在早先一些PLC还不允许出现双线圈的时代,要绕过双线圈的规则,有这样的一种方法。程序开头对线圈复位。后面的自动程序需要什么步骤输出线圈,就用置位指令代替线圈指令。最终扫描结束输出物理信号的时候取决于是否对线圈置位,如图8所示,程序在“步1”、“步2”、“步7”,这三个步骤时输出。
图8程序
如图8这样的程序结构,在多个地方都出现对Q0.0写入值。当然这样各种的值都是不同的,相同的话就不必多重了。我们应该小心处理逻辑关系,避免出现不希望的值。
再看一个例子,如图9所示:
图9程序
程序中把输入量传给输出量。但是当发现输出量是负数的时候,把0传给输出量。也就是说限制输出量最小值是0,不可以再小了。
双线圈和多重赋值都是自定义库程序范围中一个非常重要的思想。不能很好理解这两个概念,将会对制作库程序产生很大的障碍。
五、间接寻址
在操作数引脚(实参)上直接使用常数、绝对地址、符号这三种方式是编程中使用最多写法。另外,还有符号定义的常量和间接寻址两种方式相对比较少去使用。如图10所示:
图10程序
间接寻址包括制作指针和使用指针。
指针是指向目的区域目的地址的标记。目前,CPU除了AC、HC、L区不能作为目的区域其他区域都可以间接寻址。
在后面的文章中,都只将V区作为间接寻址的目的地址做说明或者做例子。这并不表示不能寻址到其余区域。
例子中的 ”T40” 的设定值填写的是一个间接寻址。意思就是说这个位置虽然不是一个具体的设定时间,但是,具体的值位于这个指针所指的地方。而这个地方当时是什么值,那么计时器的设定值就是什么值。
简单讲就是“你要的东西用纸(址)包着”和“你要的东西用纸(址)写着”。
现在就围绕这句话,聊一聊间接寻址。
首先说说这张包东西的纸(址),不是什么地方都可以做的。什么材料可以做这张纸?材料可以是全部V区、全部L区、AC区的AC1 AC2 AC3可以装载指针。并且只能用连续的4个字节组成的双字装载指针。起始地址不介意是奇数还是偶数。
要用纸包着东西,必须把东西放进去纸里面。MOVW 100 VW0 这里 ”100” 就是东西, ”VW0” 就是一张纸。小的东西就用一张纸包(一个字节),很大的东西就用四张纸拼成一张大纸包(一个双字)。用这件东西的人只要找到这张纸直接就得到里面的东西。
你要的东西用纸写着,那就是说事前必然有人在纸上已经写了字,后面才能有人看见。MOVD &VB400 VD4 。这里 ”VB400” 就是写在上面的字,”&” 符号是说明写着这几个字是一个地址,不是普普通通的字。”VD4” 就是一张大纸。这就是制作指针。
手里拿着这张纸后,自然要根据这张纸的指引做一些事。如图10程序最后一行指令。执行一个计时器指令时使用了” *VD2” 这张纸 ……,如果只写 ”VD2”,那么就好像是使用 ”VD2” 的值作为计时器设定值,但是事实并非如此。这里 ”VD2” 前面带*号。星号表示 ”VD2” 里面的值是指引真正包含设定值的地址。由于 ”VD2” 里面先前一个指令写入了 ”&VB0” ,所以这张纸其实是指引计时器到 ”VW0” 找寻设定值。这就是使用指针。当然这是因为是在定时器的时间设置用到指针,而这个设置变量的类型是字。而如果在MOVB输入用到相同指针 ”VD2”,那么输入的就是 ”VB0” 而不是 ”VW0” 了,因为MOVB指令需要的输入变量的是字节,也就是指针保存的是起始地址,具体使用的变量取决于指令需要。
使用间接寻址的好处在于通过计算改变指针的值,可以寻址到附近的任何地址。当指针指向VB10的时候,把指针增加10,那么指针就会指向VB20。寻址就改变到VB20。指针再减少3,那么寻址就会找到VB17。对于一片连续的V空间只要知道第一个地址,就可以寻址到连续的第n个地址。
在以后的子程序使用中将会非常多地用到间接寻址的。
子程序是整个程序的一个片段。根据编程者的需求,把一部分程序归入到一个子程序里面,完成相关的功能。整个子程序可以根据用户程序的逻辑选择调用或者不调用。调用的话就按照扫描次序扫描一次子程序,然后回到调用处。不调用的话就等于程序没有了这部分片段。在其他地方再次出现调用这个子程序的话,子程序又得到一次运行。PLC并没有限制用户调用子程序的次数,只不过调用一次就要增加一点扫描时间。必要时候就调用,没必要的时候调用了也不产生什么效益。
偶尔会看见有人在问:有几个数据需要求平均值。PLC有没有求平均的指令啊?
不好意思,200 SMART PLC没有计算平均指令。还是好好地编程写吧。
下面我试试用两种方式的子程序完成这个多个值的累加。至于除法求平均就不介绍了。现以4个字变量计算总和为例介绍。
程序要求:VW0、VW2、VW4、VW6四个值求和,结果放在VW100中。
看看先用比较简单的方法,先完成要求的功能,如图1所示:
图1程序
在子程序里面就是几个很简单的加法指令。而在主程序调用子程序的前后,加上了一些简单的传送指令。
先看子程序。子程序中三个加法指令把四个MW累加到MW8中。前面四个MW中并没有什么值。累加之后的结果MW8也没有什么实际意义。
再看主程序。题目要求的四个已知值,在调用子程序之前就传到了子程序使用的MW地址。此时调用子程序,这些MW就有了指定值。经过子程序的运算,结果就放置在MW8。然后回到调用处,再把子程序的值传到题目要求的地方,完成题目的要求。
在这个例子中,主程序的这个网络可以多次使用。在需要的地方就按照上面例子的方法使用子程序就可以再次实现求和的作用。子程序只是作为一个计算法则,而这个法则又是很简单的几个计算。当遇到比较复杂的算法的时候,并且又有机会多次调用。不妨可以试试这个程序结构。这个结构有一个优势,就是任何品牌的PLC都可以互通。哪怕就是20年前的PLC也一样可以用这个方法。不过当今的200 SMART PLC显然不推荐使用这样的方式了。这个方式的编程,只是用于说明带参数子程序的一部分参数传递原理,将在日后详细介绍。
1.调用子程序的前后,需要把参数传送到指定的地址,供子程序使用。
这些地址成为了这一个子程序的专用地址;每次调用子程序都要做这个事情。
2.子程序占用了公共资源;
当一个程序有很多个子程序的时候,消耗在这两方面的工作和资源就会很多。当一个子程序工作需要很多个参数的时候,要写很多MOV指令,也要占用很多公共资源M地址。对于200 SMART PLC,M区只有32个字节,如果需要32字节以上的区域,那就只能在比较大的V区找地方了。而当子程序很多,或者子程序是不同编程者的话,就可能会有不同子程序使用到了相同的区域。这样必然需要修改一些地址,使地址不发生重复。
假如有办法解决上述两个问题,将是一个非常美满的结果。办法是有的。先不说这个。
现在,再用现在的方式写一个子程序完成求和的功能。四个步骤就可以创建一个带参数的子程序。
步骤一:
新建一个子程序。在子程序属性里面编好子程序名称和块编号(子程序号),如图2所示。这里要求不严格,主要是编程者自己容易分辨就好。当然如果是要做成库,最好把块编号设置大些,以便于调用时不会冲突。
图2 设置块属性
第二步:
如图3所示,打开变量表。
图3 变量表
第三步:
向变量表填入需要的输入输出参数,如图4所示。
图4 设置参数
变量表的各个列的意思是:
地址:变量对应L区的绝对地址。这个地址由系统分配,不能由编程者输入。每增加一个变量或者插入一行,地址自动改变。并且如果编程当前正在用符号名编程的时候,增删行导致的地址变更,不会影响编程关系。相反,如果正在用地址编程,而你又增删过行,那么程序地址将会混乱。
符号:这个是变量的名字。如果正在用地址编程的时候,此时修改名字,不会影响已写好的程序。相反,如果正在用符号编程,此时修改名字,那么程序符号立即红色报错,程序已找不到这个名字了。
变量类型:无法修改。输入参数填写在IN行。输入/输出参数填写在IN_OUT行。输出参数填写在OUT行。临时变量在最后TEMP。填写完毕会自动增加新行。
IN类型表示子程序运行的数据来源,就像上述例子的4个加数。
OUT类型表示经过子程序的运算,得到的结果。输出在这个位置。
IN/OUT类型,组合两种类型,先输入给子程序再运算得到结果输出到相同的位置。
TEMP类型,这个类型既不输入也不输出,仅仅在子程序中充当过渡用的中间变量。
数据类型:这个参数是一个点位还是一个字节还是一个浮点数等等的性质。这个下拉选择将会影响地址栏的分配跨度。因为不同的类型占用不同的位长。
注释:给自己看的一些关于本行变量的备忘信息。
到此,一个带参数的子程序就已经完成了。虽然并没开始编写子程序的内容,但是你已经可以观察一下由主程序调用这个子程序的样子。如图5所示。
图5 子程序调用
网络1是当子程序没有填写变量表的时候的样子,它不带任何参数。网络2是刚才建立了变量表的求和子程序。此时的子程序未确定任何实质性的参数地址。点击每个问号地方,按照题目要求左侧输入VW0;VW2;VW4;VW6,右侧输入VW100,调用基本就完成了。
子程序输入输出参数的排序会按照你刚才填写变量表的先后顺序布置。在实际程序中,你最好在向子程序编程之前先确认一下这个外观效果,每一个输入输出参数是否能够按照使用的习惯排列所有参数。例如我们表达一个长方体的尺寸的时候通常都说“长宽高”,没有人会说“高长宽”的次序的。这个就是习惯。当参数很多的时候,尽量按照有关习惯填写变量表的顺序。可以用插入删除行的方法编辑次序。
第四步:对子程序编程
子程序内的加法过程的编程在此就省略了。其实就是图1的子程序。只不过是将全部的MW改写成LW就可以了。
好了,一个带参数的子程序就完整建立完毕了。它将会出现在指令树里面项目的程序块里面。它因为本身就是一个子程序,所以也是显示为一个子程序。
后面的内容更精彩。可能不是你想象的那回事哦~
一份程序中建立多个子程序,每个子程序完成相对比较独立的功能。而子程序也是围绕主程序的要求,并且还根据主程序给出的条件完成这个要求。此时主程序和子程序之间或者说子程序和它下级的子程序之间,就必然有一些数据的交流。下面将介绍一下这种参数的传递。
再提一下变量表,子程序里面的变量表,这是最重要的上下级程序之间数据传递的方式,但不是唯一的方式。主程序把将要给子程序的数值摆放在一个或一片约定的全局地址,然后子程序读取这些全局的地址……这种做法作为传递参数,最简单不过。但是,这里不讨论这样的传递方式。
现在随便做一个子程序,观察一下在主程序中调用时候的外观。
图1
图2
如图1所示是在主程序里面调用SBR_0的情况,还有一个常见的计数器指令。如图2所示是SBR_0中变量表所填入的变量设置。
图1中一个标注SBR_0的指令块跟计数器指令外观很相似。但是各自的接口和参数都不同。它们都有触点接口和数据接口。现在看看子程序的调用,主程序通过什么渠道把参数传递到子程序。
SBR_0左边上方开始有两个连接着开关触点的接口。
【EN】有能流表示执行这个子程序。没有能流的话将从子程序门口经过,不进入子程序。一个子程序就算没有任何参数都会有这个接口的。
【点输入1】下边也是一个能流型的接口,表示这里有一个开关量的能流输入。
当鼠标指向这个指令块的时候,会弹出一个框,如图3所示。这里说明了所有参数的变量类型(in;in/out;out)和数据类型(b;B;W;D)。
图 3
【字输入】从弹框得知这是一个字型的输入参数。这里接口必须填写一个字型的变量或者常数。
【点输入2】这也是一个布尔量的输入参数,但是因为有其余类型的参数分割,导致没有从开始处连续排列布尔量的输入参数,所以这个布尔量输入也显示为数据接口形式。
【字节入出】从弹框得知这是一个输入输出双向的参数,需要填入一个字节型的变量。
【字输出】这个参数在右侧,位置上已经表明这是一个输出型的参数。
综上所述,输入型的参数将安排在左侧的最上方,并且如果上方有连续的开关量,都会以能流的形式连接这个参数。接下来是数据型的输入参数接口。然后下方是双向的参数接口。右边一律是数据接口形式连接所有输出参数。
上级程序就是通过这样的接口把子程序所需的参数传入到子程序的,再进入到子程序里面。接下来看看子程序方面,得到的参数将会是如何出现的。
观察上面图2这个变量表。自从建立了这个变量表开始,子程序里面的L地址的前面部分就已经成为了子程序参数的载体。L地址从第一个字节开始,往后安排IN参数;IN/OUT参数;OUT参数。按照字节为最小占用单位来安排能流接口的参数,从L0.0开始向更高位连续安排,EN不占用地址。对于布尔量,一个布尔量点占用整个字节,八个以内的连续布尔量点也占用一个字节。下一个非布尔量参数或者不同类型变量(IN、IN/OUT、OUT)从下一个字节开始安排。所以【点输入1】参数分配到L0.0,【字输入】参数分配到LW1。【字节入出】参数分配到LB4。然后子程序使用上级传递的参数就只要读取这些L地址就可以得到。
外面的情况了解了,里面的情况也了解了,参数传递……没说完,还有最不为人知的一部分。
——中间的过程。
中间过程就像一份快递的工作,在上级下级程序之间搬运数据。只要子程序被调用,那么快递就会把指定的数据传送到子程序内。子程序结束后,快递又会把指定的参数传送到上级程序的接口。要是没有接口参数自然没有这个岗位。
作为用户程序,只要上级程序把参数放置到位,又在子程序里面存取对应的L地址,中间怎么从上级传达到下级是无需做任何事情的。这个过程由系统代劳了。但不等于不用理解这个过程。你不知道的事情可能恰恰就在这里。做以下的程序实验一下。
图4
建立三个子程序,各自就只有变量表填写了一行参数,如图4所示。子程序里面无任何用户程序。主程序编辑这样的调用逻辑。把程序下载到PLC,看看执行会怎样?
PLC运行后,由于程序存在QB0和M0.0,没有任何用到的其他地址了,所以我们的焦点就关注着这两个地方,而QB0更是最容易看见。此时输出点还是熄灭的。用状态表把M0.0写入1,与此同时QB0也显示2#10010000(注意硬件的灯高位在右侧,书写二进制高位在左侧。)然后把M0.0写入0,此时QB0变成显示2#11001100。我们再次把M0.0写入1之后QB0再次显示2#10010000 。
为什么输出点会有这样的三种状态呢?用户程序完全没有对输出点做过逻辑和赋值,而子程序里面也没有任何程序。能够有动作的只能是系统的动作了。究竟会是什么时候使输出点得到了一些值?程序里面唯一跟QB0有点关系的就是SBR2子程序了。为什么SBR0、SBR1两个子程序的值会到了SBR2子程序里面去的?那么就要理一理参数传递的中间过程了。
运行初期,网络1不接通,网络2没产生边沿信号。前两个子程序都没有调用。唯独第三个子程序SBR2一直在调用。
接通M0.0,SBR0得到了执行。在进入子程序后,运行子程序用户指令之前,系统把引脚上的IN参数值复制到了LB0。然后执行子程序的用户程序,由于没有程序可运行,退出了子程序。
网络2没有执行子程序。网络3一直在执行。SBR2子程序没有输入参数,没有子程序内容,离开子程序之前,系统必须要给一个值到输出参数。系统的这个动作不会因为程序中有没有内容或者说程序执行的怎么样,而不去做这个传送,它是必然会做这个动作的。那么这个时候,LB0到底是一个什么值呢?我们就要追溯到这个LB0最后赋值的是什么地方。之前由于执行SBR0输入参数使LB0曾经有过2#10010000这个值。这个值由于遵从多重赋值的原因,这个值在当时产生,一直搁置到现在,被SBR2捞上来了,然后就给了QB0。当M0.0恢复到0时,SBR1前的上升沿发生了,调用了一次。就这一次,把LB0写入了2#11001100。在没有什么地方再给LB0写入值的情况下,LB0将不会改变。(当一个子程序不调用的时候,并不会由此使得某些线圈、某些地址自动复位为零,但是仍有不少的人有这样的错觉。)大家也可以参考M0.0,编程软件写入了的值可以一直保持住的。你没有去更改它,还有谁去修改它?所以把一个值写入到某地址之后,就可以一直保持住,SBR2也就可以输出2#11001100这个值。
手册并没有说明局部储存区的值是从何而来,手册上面有一段话,如图5所示。
图 5
当手册没有清楚说明的情况下,我会假设一种较为合理的假设,然后在日后的编程中一直证实这个假设,并使用这种假设,同时一直关注着这个问题。假如一直使用都没有明确显示这种假设不成立,将认为这是真的,继续使用。我总觉得PLC只会按照有限的法则以固定的方法实施一些运算。既然一个假设一直没有被推翻,那么这个假设可能永远也不会被推翻。
免责声明:本文转自网络,版权归原作者所有,如涉及作品版权问题,请及时与我们联系删除,谢谢!
【测漏电最快的方法】10个电工有9个不愿教,学会这个方法快速判断!
戳“阅读原文”学习更多行业知识!
你的点赞,关注,转发都是对小编最大的鼓励💪